#include <sys/mman.h>
#include <cstring> // memset()

#include "./PageCache.h"

namespace MemoryPool
{
    PageCache &PageCache::getInstance()
    {
        static PageCache instance{};
        return instance;
    }

    void *PageCache::allocSpan(const size_t needPageNum)
    {
        std::lock_guard<std::mutex> lock(this->mtx_);

        // 根据需要页数，查找合适的空闲span链表->取头节点空闲span
        // lower_bound()比较第一个元素页数，返回第一个大于或等于needPageNum元素的迭代器
        auto it = this->freeSpans_.lower_bound(needPageNum);
        if (it == this->freeSpans_.end()) // 没找到
        {
            // 向系统申请
            void *systemMemAddr = this->allocSystemMem(needPageNum);
            if (!systemMemAddr)
            {
                return nullptr;
            }

            // 创建新span
            Span *span = new Span;
            span->pageAddr = systemMemAddr;
            span->pageNum = needPageNum;
            span->next = nullptr;

            // 记录span信息用于回收
            this->usedSpans_[systemMemAddr] = span;

            // 返回需要的地址
            return systemMemAddr;
        }

        // 找到
        Span *spanHead = it->second; // 合适的空闲链表的头节点

        // 将取出的span从原有的空闲链表freeSpans_[it->first]中移除
        // 即如果该空闲链表还有，就接住it->first页数 = span->next空闲链表头节点的下一个节点作为新头节点
        // 否则，删除该页数-空闲链表
        if (spanHead->next)
        {
            this->freeSpans_[it->first] = spanHead->next;
        }
        else
        {
            this->freeSpans_.erase(it);
        }

        // 如果span大于需要的pageNum则进行分割
        if (spanHead->pageNum > needPageNum)
        {
            Span *newSpan = new Span;
            // 地址是一个B，用char *操作
            newSpan->pageAddr = static_cast<char *>(spanHead->pageAddr) + needPageNum * PAGE_SIZE; // 超出部分地址
            newSpan->pageNum = spanHead->pageNum - needPageNum;                                    // 超出数量
            newSpan->next = nullptr;

            // 将超出部分放回空闲Span*列表头部
            auto &newSpanHead = this->freeSpans_[newSpan->pageNum]; // 取该数量的链表，map[]没有会创建值为nullptr
            newSpan->next = newSpanHead;                            // 头插
            newSpanHead = newSpan;                                  // 修改指针指向

            spanHead->pageNum = needPageNum; // 修改待返回span数量就是要请求数量
        }

        // 记录span信息用于回收
        this->usedSpans_[spanHead->pageAddr] = spanHead;

        // 返回需要的地址
        return spanHead->pageAddr;
    }

    void *PageCache::allocSystemMem(const size_t needPageNum)
    {
        size_t needSize = needPageNum * PAGE_SIZE;

        // mmap分配内存
        // 映射的起始地址空则自动选择；大小；可读可写
        // 私有：内存修改不会映射到原始文件和设备，匿名：映射不是来自文件；fd映射的文件或设备；偏移量
        void *memAddr = mmap(nullptr, needSize, PROT_READ | PROT_WRITE,
                             MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
        if (memAddr == MAP_FAILED)
        {
            return nullptr;
        }

        // 清零内存
        memset(memAddr, 0, needSize);
        return memAddr;
    }

    void PageCache::deallocSpan(void *spanAddr, const size_t needPageNum)
    {
        std::lock_guard<std::mutex> lock(this->mtx_);

        // 查找对应的span，没找到代表不是PageCache分配的内存，直接返回
        auto it = this->usedSpans_.find(spanAddr);
        if (it == this->usedSpans_.end())
        {
            return;
        }

        // 找到就获取span
        Span *span = it->second;

        // 尝试合并相邻的span
        // TODO: 实际上应该自动合并？可以用span->pageNum数量获取下一个地址
        // TODO：CentralCache未调用deallocSpan()
        void *nextSpanAddr = static_cast<char *>(spanAddr) + needPageNum * PAGE_SIZE;
        auto nextIt = this->usedSpans_.find(nextSpanAddr);
        if (nextIt != this->usedSpans_.end())
        {
            // 找到就获取span
            Span *nextSpan = nextIt->second;

            // 1. 首先检查nextSpan是否在空闲链表中
            bool found{false};
            auto &nextSpanHead = this->freeSpans_[nextSpan->pageNum];

            // 检查是否是头节点
            if (nextSpan == nextSpanHead)
            {
                nextSpanHead = nextSpan->next; // 是头节点，则头指针指向下一个节点，将nextSpan从空闲链表中移除
                found = true;
            }
            else if (nextSpanHead) // 不是头节点，只有在链表非空时才遍历
            {
                Span *curr = nextSpanHead;
                while (curr->next)
                {
                    if (curr->next == nextSpan)
                    {
                        curr->next = nextSpan->next; // 将nextSpan从空闲链表中移除
                        found = true;
                        break;
                    }
                    curr = curr->next;
                }
            }

            // 2. 只有在找到nextSpan的情况下才进行合并
            if (found)
            {
                // 合并span
                span->pageNum += nextSpan->pageNum;
                this->usedSpans_.erase(nextSpanAddr);
                delete nextSpan;
            }

            // TODO: 合并后连续大内存到达阈值，可以考虑归还系统
        }

        // 将合并后的span通过头插法插入空闲列表
        auto &spanHead = this->freeSpans_[span->pageNum];
        span->next = spanHead;
        spanHead = span;
    }
} // namespace MemoryPool